/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { ts, Local, Type, ArkMethod, ArkFile, AbstractBinopExpr, LocalSignature, ArkAssignStmt, MethodSignature, Cfg, FullPosition, LineColPosition, ArkNormalBinopExpr, LiteralType, AstTreeUtils } from "arkanalyzer/lib";
import Logger, { LOG_MODULE_TYPE } from 'arkanalyzer/lib/utils/logger';
import { BaseChecker, BaseMetaData } from "../BaseChecker";
import { Defects, IssueReport } from "../../model/Defects";
import { ClassMatcher, FileMatcher, MatcherCallback, MatcherTypes, MethodMatcher } from "../../matcher/Matchers";
import { Rule } from "../../model/Rule";
import { RuleListUtil } from "../../utils/common/DefectsList";
type localType = {
    name: string,
    originalType: {
        currType?: LiteralType[],
        types?: LiteralType[],
        name?: string
    },
    signature: LocalSignature
}
type LType = {
    literalName: string,
    name: string
}
type classSignature = {
    classSignature: {
        className: string,
        declaringFilesSignature: {
            fileName: string,
            hashcode: number,
            projectName: string
        },
        declaringNamespaceSignature: {}
    },
    realGenericTypes: {}
}
type RightOp = {
    args: [],
    base: {
        declaringStmt: ArkAssignStmt,
        name: string,
        originalValue: string,
        type: Type,
        usedStmts: ArkAssignStmt[]
    },
    methodSignature: MethodSignature,
    realGenericTypes: Type
}
type RightOp1 = {
    declaringStmt: ArkAssignStmt,
    name: string,
    originalValue: string,
    type: Type,
    usedStmts: ArkAssignStmt[]
}
type LeftOp = {
    declaringStmt: ArkAssignStmt,
    name: string,
    originalValue: string,
    type: Type,
    usedStmts: ArkAssignStmt[]
}
type usedStmt = {
    cfg: Cfg,
    leftOp: Local,
    operandOriginalPositions: FullPosition[],
    originalPosition: LineColPosition,
    originalText: string,
    rightOp: ArkNormalBinopExpr
}
const defaultOptions: Options = {
    allowDefaultCaseForExhaustiveSwitch: true,
    requireDefaultForNonUnion: false
}
interface Options {
    allowDefaultCaseForExhaustiveSwitch: boolean,
    requireDefaultForNonUnion: boolean
}
const logger = Logger.getLogger(LOG_MODULE_TYPE.HOMECHECK, 'SwitchExhaustivenessCheck');
const gmetaData: BaseMetaData = {
    severity: 2,
    ruleDocPath: "docs/switch-exhaustiveness-check.md", // TODO: support url
    description: "Require switch-case statements to be exhaustive with union type."
};
export class SwitchExhaustivenessCheck implements BaseChecker {
    readonly metaData: BaseMetaData = gmetaData;
    public rule: Rule;
    public defects: Defects[] = [];
    public issues: IssueReport[] = [];
    private fileMatcher: FileMatcher = {
        matcherType: MatcherTypes.FILE,
    };
    private classMatcher: ClassMatcher = {
        file: [this.fileMatcher],
        matcherType: MatcherTypes.CLASS
    };
    private methodMatcher: MethodMatcher = {
        matcherType: MatcherTypes.METHOD,
        class: [this.classMatcher]
    };
    public registerMatchers(): MatcherCallback[] {
        const matchBuildCb: MatcherCallback = {
            matcher: this.methodMatcher,
            callback: this.check
        }
        return [matchBuildCb];
    }
    private sourceFileCache = new Map<string, ts.SourceFile>();
    public check = (method: ArkMethod) => {
        // 将规则选项转换为Options类型
        let options: Options;
        if (this.rule && this.rule.option.length > 0) {
            options = this.rule.option[0] as Options;
        } else {
            options = defaultOptions;
        }
        //数据类型
        let typeName;
        //定义的参数数组
        let typesSet = new Set<string>();
        let classTypeSet = new Set<string>();
        //使用type定义的变量中的数据数组
        let typeNameSet = new Set<string>();
        let arrayLength = new Set<string>();
        //对定义的参数进行计数
        let count = 0;
        let classCount = 0;
        //存储变量名的数组
        let variableNameSet = new Set<string>();
        let trueSwitch = new Set<string>();
        let declareClass = method.getDeclaringArkClass();
        //获取当前方法的声明文件;
        const arkFile = declareClass.getDeclaringArkFile();
        //获取当前方法的签名;
        const arkFileSignature = arkFile.getFileSignature();
        //获取当前方法的声明类;
        const classes = arkFile.getClasses();
        // 构建映射
        const signatureMap = new Map();
        const regex = /\b(var|let|const)\s+(\w+)\b/g;
        //将当前文件的签名和类映射起来
        signatureMap.set(arkFileSignature, classes);
        // 提前过滤不需要处理的类型
        const skipTypes = new Set(['string', 'number', 'boolean', 'undefined', 'null', 'void']);
        //获取当前方法的代码;
        const code = arkFile.getCode() ?? '';
        for (const Class of classes) {
            if(!Class){
                continue;
            }
            const classSignature = Class.getSignature();
            const staticFields = Class.getStaticFields(signatureMap);
            if (staticFields && staticFields.length > 0) {
                const typeName = classSignature?.getClassName();
                typeNameSet.add(typeName);
                for (const staticFiled of staticFields) {
                    let fieldName = staticFiled.getSignature().getFieldName();
                    if (classTypeSet.size > 2) {
                        classTypeSet.clear();
                    }
                    if (!classTypeSet.has(fieldName)) {
                        classTypeSet.add(fieldName);
                        classCount++;
                    }
                }
            }
        }
        if (method instanceof ArkMethod) {
            //获取type定义的名称和类型数组;
            const defaultArkMethod = method.getDeclaringArkClass()?.getDefaultArkMethod();
            const locals = defaultArkMethod?.getBody()?.getLocals() ?? [];
            const localsValue = locals?.values();
            if(!localsValue){
                return;
            }
            for (const localValue of localsValue) {
                let type = localValue?.getType() as unknown as localType;
                if (skipTypes.has(type.name)){
                    continue;
                }
                let usedStmts = localValue?.getUsedStmts();
                typeName = type?.name;
                if (typeName) {
                    typeNameSet.add(typeName);
                    const originalType = type?.originalType;
                    if(!originalType){
                        continue; 
                    }
                    //将type定义的数据放进数组中
                    const types = originalType.types;
                    if(!types){
                        continue;
                    }
                    for (const Type of types) {
                        const type = Type as unknown as LType;
                        const literalName = type?.literalName;
                        if (typesSet.has(literalName)) {
                            continue;
                        }
                        typesSet.add(literalName);
                        count++;
                    }
                }
                const stmts = method.getBody()?.getCfg().getStmts()??[];
                for (let stmt of stmts) {
                    // 如果语句不是ArkAssignStmt类型的，则跳过
                    if (!(stmt instanceof ArkAssignStmt)) {
                        continue;
                    }
                    let leftOp = stmt?.getLeftOp();
                    let leftOpType = leftOp?.getType() as unknown as localType;
                    let leftOpTypeName = leftOpType?.name;
                    let classType = leftOp?.getType() as unknown as classSignature;
                    let signature = classType?.classSignature;
                    let className = signature?.className;
                    let text = stmt?.getOriginalText() ?? '';
                    if (className) {
                        for (let typeName of typeNameSet) {
                            if(typeName !== className) {
                                continue;
                            }
                            if (variableNameSet.has(leftOp.toString())) {
                                continue;
                            }
                            //获取枚举类型的变量名;
                            variableNameSet.add(leftOp.toString());
                        }
                    }
                    for (let typeName of typeNameSet) {
                        if (leftOpTypeName == typeName) {
                            if (variableNameSet.has(leftOp.toString())) {
                                continue;
                            }
                            //获取联合类型的变量名;
                            variableNameSet.add(leftOp.toString());
                        }
                    }
                    const rightOp = stmt?.getRightOp() as unknown as RightOp;
                    const rightOpBase = rightOp?.base;
                    if (rightOpBase) {
                        const baseName = rightOpBase?.name;
                        if (baseName == 'Math') {
                            const LeftOp = stmt?.getLeftOp() as unknown as LeftOp;
                            const LeftOpUsedStmts = LeftOp?.usedStmts;
                            for (let usedStmt of LeftOpUsedStmts) {
                                let UsedStmt = usedStmt as unknown as usedStmt;
                                const usedStmtsRightOp = UsedStmt?.rightOp;
                                if (usedStmtsRightOp instanceof AbstractBinopExpr) {
                                    const rightOp1 = usedStmtsRightOp?.getOp1() as unknown as RightOp1;
                                    const rightOp2 = usedStmtsRightOp?.getOp2() as unknown as RightOp1;
                                    const rightOp1Type = rightOp1?.type;
                                    const rightOp2Type = rightOp2?.type;
                                    if (rightOp1Type?.toString() == 'unknown' && rightOp2Type) {
                                        const rightOp2Value = rightOp2?.toString();
                                        arrayLength.add(rightOp2Value);
                                    }
                                    if (rightOp2Type?.toString() == 'unknown' && rightOp1Type) {
                                        const rightOp1Value = rightOp1?.toString();
                                        arrayLength.add(rightOp1Value);
                                    }
                                }
                            }
                        }
                    }
                    if(usedStmts?.length == 0) {
                        continue;
                    }
                    for (let usedStmt of usedStmts) {
                        let exprs = usedStmt?.getExprs() ?? [];
                        for (const expr of exprs) {
                            let exprType = expr?.getType();
                            let exprTypeString = exprType?.getTypeString();
                            let exprOp = expr?.getUses();
                            let exprOp1 = exprOp[0] ?? '';
                            let exprOp2 = exprOp[1] ?? '';
                            let exprOp1String = exprOp1?.toString();
                            let exprOp2String = exprOp2?.toString();
                            if (exprTypeString !== 'boolean') {
                                continue;
                            }
                            //type联合类型判断
                            if (variableNameSet.has(exprOp1String) && typesSet.has(exprOp2String)) {
                                if (usedStmts.length !== typesSet.size && !this.checkSwitchHasDefaultCase(code, exprOp1String)) {
                                    this.addIssueReport(code, arkFile, exprOp1String);
                                }
                            }
                            if (variableNameSet.has(exprOp1String) && variableNameSet.has(exprOp2String)) {
                                if (usedStmts.length == classTypeSet.size || this.checkSwitchHasDefaultCase(code, exprOp1String)) {
                                    if (!trueSwitch.has(exprOp1String)) {
                                        trueSwitch.add(exprOp1String);
                                    }
                                }
                                if (options.allowDefaultCaseForExhaustiveSwitch == false) {
                                    if (usedStmts.length < classTypeSet.size && this.checkSwitchHasDefaultCase(code, exprOp1String)) {
                                        if (!trueSwitch.has(exprOp1String)) {
                                            trueSwitch.add(exprOp1String);
                                        }
                                    }
                                }
                            }
                            //enum枚举类型判断
                            if (variableNameSet.has(exprOp1String) && variableNameSet.has(exprOp2String)) {
                                if (usedStmts.length !== classTypeSet.size && !this.checkSwitchHasDefaultCase(code, exprOp1String)) {
                                    //排除特殊情况
                                    if (usedStmts.length == 1) {
                                        for (const UsedStmt of usedStmts) {
                                            let exprs = UsedStmt?.getExprs() ?? [];
                                            for (const Expr of exprs) {
                                                let ExprOp = Expr?.getUses();
                                                let ExprOp1 = ExprOp[0] ?? '';
                                                let ExprOp1String = ExprOp1?.toString();
                                                if (trueSwitch.has(ExprOp1String)) {
                                                    continue;
                                                }
                                                this.addIssueReport(code, arkFile, ExprOp1String);
                                            }
                                        }
                                    } else {
                                        this.addIssueReport(code, arkFile, exprOp1String);
                                    }
                                }
                                if (options.allowDefaultCaseForExhaustiveSwitch == false) {
                                    if (usedStmts.length == classTypeSet.size && this.checkSwitchHasDefaultCase(code, exprOp1String)) {
                                        //排除特殊情况
                                        if (usedStmts.length == 1) {
                                            for (const UsedStmt of usedStmts) {
                                                let exprs = UsedStmt?.getExprs() ?? [];
                                                for (const Expr of exprs) {
                                                    let ExprOp = Expr?.getUses();
                                                    let ExprOp1 = ExprOp[0] ?? '';
                                                    let ExprOp1String = ExprOp1?.toString();
                                                    if (trueSwitch.has(ExprOp1String)) {
                                                        continue;
                                                    }
                                                    this.addIssueReport(code, arkFile, ExprOp1String);
                                                }
                                            }
                                        } else {
                                            this.addIssueReport(code, arkFile, exprOp1String);
                                        }
                                    }
                                }
                            }
                            if (!variableNameSet.has(exprOp1String) && !typesSet.has(exprOp2String)) {
                                if (options.requireDefaultForNonUnion == true) {
                                    if(arrayLength.size == 0) {
                                        continue;
                                    }
                                    //非联合类型非枚举判断
                                    //获取非枚举类型，非联合类型的变量名
                                    let matches = text.matchAll(regex);
                                    if (matches) {
                                        for (let match of matches) {
                                            const targetMatch = match[2]
                                            if (!variableNameSet.has(targetMatch)) {
                                                variableNameSet.add(targetMatch);
                                            }
                                        }
                                    }
                                    if(!rightOpBase){
                                        continue;
                                    }
                                    const baseName = rightOpBase?.name;
                                    if(baseName !== 'Math') {
                                        continue;
                                    }
                                    if (!variableNameSet.has(exprOp1String)) {
                                        continue;
                                    }
                                    if (arrayLength.size == 0) {
                                        continue;
                                    }
                                    for (const lengthValue of arrayLength) {
                                        if (usedStmts.length.toString() !== lengthValue && !this.checkSwitchHasDefaultCase(code, exprOp1String)) {
                                            this.addIssueReport(code, arkFile, exprOp1String);
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
    /**
   * 检查 TypeScript 代码中 switch 代码块中的参数位置。
   * @param code 要检查的 TypeScript 代码
   * @returns 包含 switch 语句参数位置的对象数组
   */
    public checkSwitchParams(code: string, name: string): { line: number, character: number }[] {
        // 创建一个源代码文件对象
        let sourceFile = this.sourceFileCache.get(code);
        if (!sourceFile) {
            sourceFile = AstTreeUtils.getASTNode('temp.ts', code);
            this.sourceFileCache.set(code, sourceFile);
        }
        // 初始化一个数组来存储 switch 语句参数的位置
        const switchParamsPositions: { line: number, character: number }[] = [];

        // 定义一个函数来检查节点
        function checkNode(node: ts.Node) {
            // 如果节点是一个 switch 语句
            if (ts.isSwitchStatement(node)) {
                // 获取 switch 语句的参数
                const switchParam = node.expression;
                if (switchParam) {
                    const paramText = switchParam.getText(sourceFile);
                    if (paramText.includes(name)) {
                        if(!sourceFile){
                            return;
                        }
                        // 获取参数的起始位置
                        const { line, character } = sourceFile.getLineAndCharacterOfPosition(switchParam.getStart());
                        // 将参数的位置添加到数组中
                        switchParamsPositions.push({ line: line + 1, character: character + 1 });
                    }
                }
            }
            // 递归检查节点的所有子节点
            ts.forEachChild(node, checkNode);
        }

        // 从源代码文件的根节点开始检查
        checkNode(sourceFile);

        // 返回所有 switch 语句参数的位置
        return switchParamsPositions;
    }
    /**
     * 检查 TypeScript 代码中 switch 代码块中是否存在 default case。
     * @param code 要检查的 TypeScript 代码
     * @returns 如果存在 default case，则返回 true；否则返回 false。
     */
    public checkSwitchHasDefaultCase(code: string, name: string): boolean {
        let sourceFile = this.sourceFileCache.get(code);
        if (!sourceFile) {
            sourceFile = AstTreeUtils.getASTNode('temp.ts', code);
            this.sourceFileCache.set(code, sourceFile);
        }
        let hasDefaultCase = false;

        function checkNode(node: ts.Node) {
            if (ts.isSwitchStatement(node)) {
                // 获取 switch 语句的参数表达式
                const switchParam = node.expression;
                if (switchParam) {
                    // 获取参数表达式的文本内容
                    const paramText = switchParam.getText(sourceFile);

                    // 检查参数文本是否与传入的 name 相匹配
                    if (paramText.trim() === name) {
                        // 检查当前 switch 语句是否有 default case
                        const cases = node.caseBlock.clauses;
                        if (cases.some(clause => ts.isDefaultClause(clause))) {
                            hasDefaultCase = true;
                        }
                        // 如果只需要找到一个匹配的 switch 语句，可以提前返回
                        // return true; // Uncomment this line if you want to stop checking after finding the first match
                    }
                }
                // 无论是否找到匹配的 switch 语句，都继续递归检查其他节点
                ts.forEachChild(node, checkNode);
            } else {
                // 对于非 switch 语句的节点，也继续递归检查
                ts.forEachChild(node, checkNode);
            }
        }

        checkNode(sourceFile);
        return hasDefaultCase;
    }
    private addIssueReport(code: string, arkFile: ArkFile, name: string) {
        const severity = this.rule.alert ?? this.metaData.severity;
        const warnInfo = this.getLineAndColumn(code, arkFile, name);
        this.metaData.description = this.metaData.description;
        if (warnInfo) {
            const filePath = arkFile.getFilePath();
            let defects = new Defects(warnInfo.line, warnInfo.startCol, warnInfo.endCol, this.metaData.description, severity, this.rule.ruleId, filePath,
                this.metaData.ruleDocPath, true, false, false);
            this.issues.push(new IssueReport(defects, undefined));
            RuleListUtil.push(defects);
        }
    }
    private getLineAndColumn(code: string, arkFile: ArkFile, name: string) {
        if (arkFile) {
            const positions = this.checkSwitchParams(code, name);
            if (positions.length > 0) {
                for (const position of positions) {
                    const line = position.line;
                    const startCol = position.character;
                    const endCol = position.character;
                    const originPath = arkFile.getFilePath();
                    return { line: line, startCol, endCol, filePath: originPath }
                }
            }
        }
        return null;
    }
}